<template>
	<view class="scroll-wrap" ref="wrap">
		<view v-if="navigation" :style="leftSwitch" :class="leftSwitchClass" @click="leftSwitchClick">
			<slot name="left-switch"></slot>
		</view>
		<view v-if="navigation" :style="rightSwitch" :class="rightSwitchClass" @click="rightSwitchClick">
			<slot name="right-switch"></slot>
		</view>
		<view class="real-box" ref="realBox" :style="pos" @mouseenter="enter" @mouseleave="leave"
			@touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
			<view class="slot-list" ref="slotList" :style="float">
				<slot></slot>
			</view>
			<view :style="float" v-if="copyHtml">
				<slot></slot>
			</view>
		</view>
	</view>
</template>

<script>
	/**
	 * @desc AnimationFrame简单兼容hack
	 */
	const cancelAnimationFrame = (function() {
		return (
			function(id) {
				return clearTimeout(id);
			}
		);
	})();
	const requestAnimationFrame = (function() {
		return (
			function(callback) {
				return setTimeout(callback, 1000 / 60);
			}
		);
	})();

	/**
	 * @desc 深浅合并拷贝
	 */
	const copyObj = function copyObj(obj) {
		//   筛选，如果是引用数据类型则进入if
		if (Array.isArray(obj) || typeof obj === 'object') {
			// 创建变量接受深拷贝数据，如果obj是数组，则变量初始化为数组
			const cloneObj = Array.isArray(obj) ? [] : {};
			//   将变量进行遍历
			Object.keys(obj).forEach((key) => {
				//   如果被拷贝对象为引用数据，则将递归后的数据放入对应的键内
				if (Array.isArray(obj[key]) || typeof obj[key] === 'object') {
					cloneObj[key] = copyObj(obj[key]);
				} else {
					// 否则直接拷贝键值
					cloneObj[key] = obj[key];
				}
			});
			//   然后返回拷贝后的对象
			return cloneObj;
		}
		// 普通数据直接返回
		return obj;
	}
	/**
	 * @desc 判断数组是否相等
	 * @param {arr1,arr2}
	 * @return {Boolean}
	 */
	const arrayEqual = (arr1, arr2) => {
		if (arr1 === arr2) return true;
		if (arr1.length !== arr2.length) return false;
		for (let i = 0; i < arr1.length; ++i) {
			if (arr1[i] !== arr2[i]) return false;
		}
		return true;
	}
	export default {
		name: 'VueSeamlessScroll',
		props: {
			data: {
				type: Array,
				default: () => {
					return [];
				},
			},
			classOption: {
				type: Object,
				default: () => {
					return {};
				},
			},
		},
		data() {
			return {
				xPos: 0,
				yPos: 0,
				delay: 0,
				copyHtml: '',
				height: 0,
				width: 0, // 外容器宽度
				realBoxWidth: 0, // 内容实际宽度
			};
		},
		computed: {
			leftSwitchState() {
				return this.xPos < 0;
			},
			rightSwitchState() {
				return Math.abs(this.xPos) < this.realBoxWidth - this.width;
			},
			leftSwitchClass() {
				return this.leftSwitchState ? '' : this.options.switchDisabledClass;
			},
			rightSwitchClass() {
				return this.rightSwitchState ? '' : this.options.switchDisabledClass;
			},
			leftSwitch() {
				return {
					position: 'absolute',
					margin: `${this.height / 2}px 0 0 -${this.options.switchOffset}px`,
					transform: 'translate(-100%,-50%)',
				};
			},
			rightSwitch() {
				return {
					position: 'absolute',
					margin: `${this.height / 2}px 0 0 ${this.width + this.options.switchOffset}px`,
					transform: 'translateY(-50%)',
				};
			},
			float() {
				return this.isHorizontal ? {
					float: 'left',
					overflow: 'hidden'
				} : {
					overflow: 'hidden'
				};
			},
			pos() {
				console.log({
					transform: `translate(${this.xPos}px,${this.yPos}px)`,
					transition: `all ${this.ease} ${this.delay}ms`,
					overflow: 'hidden'
				})
				return {
					transform: `translate(${this.xPos}px,${this.yPos}px)`,
					transition: `all ${this.ease} ${this.delay}ms`,
					overflow: 'hidden',
				};
			},
			defaultOption() {
				return {
					step: 1, // 步长
					limitMoveNum: 5, // 启动无缝滚动最小数据数
					hoverStop: true, // 是否启用鼠标hover控制
					direction: 1, // 0 往下 1 往上 2向左 3向右
					openTouch: true, // 开启移动端touch
					singleHeight: 0, // 单条数据高度有值hoverStop关闭
					singleWidth: 0, // 单条数据宽度有值hoverStop关闭
					waitTime: 1000, // 单步停止等待时间
					switchOffset: 30,
					autoPlay: true,
					navigation: false,
					switchSingleStep: 134,
					switchDelay: 400,
					switchDisabledClass: 'disabled',
					isSingleRemUnit: false, // singleWidth/singleHeight 是否开启rem度量
				};
			},
			options() {
				return copyObj({
					...this.defaultOption,
					...this.classOption
				});
			},
			navigation() {
				return this.options.navigation;
			},
			autoPlay() {
				if (this.navigation) return false;
				return this.options.autoPlay;
			},
			scrollSwitch() {
				return this.data.length >= this.options.limitMoveNum;
			},
			hoverStopSwitch() {
				return this.options.hoverStop && this.autoPlay && this.scrollSwitch;
			},
			canTouchScroll() {
				return this.options.openTouch;
			},
			isHorizontal() {
				return this.options.direction > 1;
			},
			baseFontSize() {
				return this.options.isSingleRemUnit ?
					parseInt(window.getComputedStyle(document.documentElement, null).fontSize, 10) :
					1;
			},
			realSingleStopWidth() {
				return this.options.singleWidth * this.baseFontSize;
			},
			realSingleStopHeight() {
				return this.options.singleHeight * this.baseFontSize;
			},
			step() {
				let singleStep;
				let step = this.options.step;
				if (this.isHorizontal) {
					singleStep = this.realSingleStopWidth;
				} else {
					singleStep = this.realSingleStopHeight;
				}
				if (singleStep > 0 && singleStep % step > 0) {
					console.error('如果设置了单步滚动,step需是单步大小的约数,否则无法保证单步滚动结束的位置是否准确。~~~~~');
				}
				return step;
			},
		},
		watch: {
			data(newData, oldData) {
				this._dataWarm(newData);
				// 监听data是否有变更
				if (!arrayEqual(newData, oldData)) {
					this.reset();
				}
			},
			autoPlay(bol) {
				if (bol) {
					this.reset();
				} else {
					this._stopMove();
				}
			},
		},
		mounted() {
			this._initMove();
		},
		beforeCreate() {
			this.reqFrame = null; // move动画的animationFrame定时器
			this.singleWaitTime = null; // single 单步滚动的定时器
			this.isHover = false; // mouseenter mouseleave 控制this._move()的开关
			this.ease = 'ease-in';
		},
		beforeDestroy() {
			this._cancle();
			clearTimeout(this.singleWaitTime);
		},
		methods: {
			reset() {
				this._cancle();
				this._initMove();
			},
			leftSwitchClick() {
				if (!this.leftSwitchState) return;
				// 小于单步距离
				if (Math.abs(this.xPos) < this.options.switchSingleStep) {
					this.xPos = 0;
					return;
				}
				this.xPos += this.options.switchSingleStep;
			},
			rightSwitchClick() {
				if (!this.rightSwitchState) return;
				// 小于单步距离
				if (this.realBoxWidth - this.width + this.xPos < this.options.switchSingleStep) {
					this.xPos = this.width - this.realBoxWidth;
					return;
				}
				this.xPos -= this.options.switchSingleStep;
			},
			_cancle() {
				cancelAnimationFrame(this.reqFrame || '');
			},
			touchStart(e) {
				if (!this.canTouchScroll) return;
				let timer;
				const touch = e.targetTouches[0]; // touches数组对象获得屏幕上所有的touch，取第一个touch
				const {
					waitTime,
					singleHeight,
					singleWidth
				} = this.options;
				this.startPos = {
					x: touch.pageX,
					y: touch.pageY,
				}; // 取第一个touch的坐标值
				this.startPosY = this.yPos; // 记录touchStart时候的posY
				this.startPosX = this.xPos; // 记录touchStart时候的posX
				if (singleHeight && singleWidth) {
					if (timer) clearTimeout(timer);
					timer = setTimeout(() => {
						this._cancle();
					}, waitTime + 20);
				} else {
					this._cancle();
				}
			},
			touchMove(e) {
				// 当屏幕有多个touch或者页面被缩放过，就不执行move操作
				if (!this.canTouchScroll || e.targetTouches.length > 1 || (e.scale && e.scale !== 1)) return;
				const touch = e.targetTouches[0];
				const {
					direction
				} = this.options;
				this.endPos = {
					x: touch.pageX - this.startPos.x,
					y: touch.pageY - this.startPos.y,
				};
				event.preventDefault(); // 阻止触摸事件的默认行为，即阻止滚屏
				const dir = Math.abs(this.endPos.x) < Math.abs(this.endPos.y) ? 1 : 0; // dir，1表示纵向滑动，0为横向滑动
				if (dir === 1 && direction < 2) {
					// 表示纵向滑动 && 运动方向为上下
					this.yPos = this.startPosY + this.endPos.y;
				} else if (dir === 0 && direction > 1) {
					// 为横向滑动 && 运动方向为左右
					this.xPos = this.startPosX + this.endPos.x;
				}
			},
			touchEnd() {
				if (!this.canTouchScroll) return;
				let timer;
				const direction = this.options.direction;
				this.delay = 50;
				if (direction === 1) {
					if (this.yPos > 0) this.yPos = 0;
				} else if (direction === 0) {
					let h = (this.realBoxHeight / 2) * -1;
					if (this.yPos < h) this.yPos = h;
				} else if (direction === 2) {
					if (this.xPos > 0) this.xPos = 0;
				} else if (direction === 3) {
					let w = this.realBoxWidth * -1;
					if (this.xPos < w) this.xPos = w;
				}
				if (timer) clearTimeout(timer);
				timer = setTimeout(() => {
					this.delay = 0;
					this._move();
				}, this.delay);
			},
			enter() {
				if (this.hoverStopSwitch) this._stopMove();
			},
			leave() {
				if (this.hoverStopSwitch) this._startMove();
			},
			_move() {
				// 鼠标移入时拦截_move()
				if (this.isHover) return;
				this._cancle(); // 进入move立即先清除动画 防止频繁touchMove导致多动画同时进行
				this.reqFrame = requestAnimationFrame(
					function() {
						const h = this.realBoxHeight / 2; // 实际高度
						const w = this.realBoxWidth / 2; // 宽度
						let {
							direction,
							waitTime
						} = this.options;
						let {
							step
						} = this;
						if (direction === 1) {
							// 上
							if (Math.abs(this.yPos) >= h) {
								this.$emit('ScrollEnd');
								this.yPos = 0;
							}
							this.yPos -= step;
						} else if (direction === 0) {
							// 下
							if (this.yPos >= 0) {
								this.$emit('ScrollEnd');
								this.yPos = h * -1;
							}
							this.yPos += step;
						} else if (direction === 2) {
							// 左
							if (Math.abs(this.xPos) >= w) {
								this.$emit('ScrollEnd');
								this.xPos = 0;
							}
							this.xPos -= step;
						} else if (direction === 3) {
							// 右
							if (this.xPos >= 0) {
								this.$emit('ScrollEnd');
								this.xPos = w * -1;
							}
							this.xPos += step;
						}
						if (this.singleWaitTime) clearTimeout(this.singleWaitTime);
						if (this.realSingleStopHeight) {
							// 是否启动了单行暂停配置
							if (Math.abs(this.yPos) % this.realSingleStopHeight < step) {
								// 符合条件暂停waitTime
								this.singleWaitTime = setTimeout(() => {
									this._move();
								}, waitTime);
							} else {
								this._move();
							}
						} else if (this.realSingleStopWidth) {
							if (Math.abs(this.xPos) % this.realSingleStopWidth < step) {
								// 符合条件暂停waitTime
								this.singleWaitTime = setTimeout(() => {
									this._move();
								}, waitTime);
							} else {
								this._move();
							}
						} else {
							this._move();
						}
					}.bind(this),
				);
			},
			_initMove() {
				this.$nextTick(() => {
					const {
						switchDelay
					} = this.options;
					const {
						autoPlay,
						isHorizontal
					} = this;
					this._dataWarm(this.data);
					this.copyHtml = ''; // 清空copy
					if (isHorizontal) {
						uni.createSelectorQuery().in(this).select('.scroll-wrap').boundingClientRect(
							data => {
								this.width = data.width;
								this.height = data.height
							}).exec()
						uni.createSelectorQuery().in(this).select('.slot-list').boundingClientRect(
							data => {
								let slotListWidth = data.width;
								// 水平滚动设置warp width
								if (autoPlay) {
									// 修正offsetWidth四舍五入
									slotListWidth = slotListWidth * 2 + 1;
								}
								// this.$refs.realBox.style.width = slotListWidth + 'px';
								this.realBoxWidth = slotListWidth;
							}).exec()

					}

					if (autoPlay) {
						this.ease = 'ease-in';
						this.delay = 0;
					} else {
						this.ease = 'linear';
						this.delay = switchDelay;
						return;
					}

					// 是否可以滚动判断
					if (this.scrollSwitch) {
						let timer;
						if (timer) clearTimeout(timer);
						this.copyHtml = '1';
						setTimeout(() => {
							uni.createSelectorQuery().in(this).select('.real-box').boundingClientRect(
								data => {
									console.log('data---', data)
									this.realBoxHeight = data.height;
									this._move();
								}).exec()

						}, 0);
					} else {
						this._cancle();
						this.yPos = this.xPos = 0;
					}
				});
			},
			_dataWarm(data) {
				if (data.length > 100) {
					console.warn(`数据达到了${data.length}条有点多哦~,可能会造成部分老旧浏览器卡顿。`);
				}
			},
			_startMove() {
				this.isHover = false; // 开启_move
				this._move();
			},
			_stopMove() {
				this.isHover = true; // 关闭_move
				// 防止频频hover进出单步滚动,导致定时器乱掉
				if (this.singleWaitTime) clearTimeout(this.singleWaitTime);
				this._cancle();
			},
		},
	};
</script>